/*
 * OpenParts
 * A dynamic-loading components framework for GWT
 * Copyright (C) 2011 Christophe Bouthier  [chris{AT}binary-gastronome{DOT}fr]
 *
 * This work is partially based on work I have done at INRIA (http://www.inria.fr) 
 * in the context of the Qualipso European Project (http://qualipso.org/),
 * The work done at INRIA is Copyright (C) 2006-2011 INRIA
 *
 * This work is distributed under the LGPL version 3
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License version 3
 * as published by the Free Software Foundation. See the GNU
 * Lesser General Public License in LGPL.txt for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package fr.openparts.OpenParts.client;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * OPParams class is used to pass an hash of parameters to parts view factories. The way to call a view factory need to be generic, but each view factories can
 * have a different number of parameters. Instead of passing a varargs Java structure, it is easier to pass an hash, associating a name to each param value,
 * like ruby named parameters way. It is then easier for the view factory to find back the information it needs.
 * <p>
 * There is two ways to build a OPParams object. The first one is to use the varargs in the constructor, passing successively a parameter name and just after
 * its associated value. This way is easier and produce less code, but is error-prone, as there should be an even number of strings passed as arguments to the
 * constructor (each parameter name should be associated with a value). The second way is to pass no arguments to the constructor, and to use the
 * {@link #addParam(String, String)} method. The addParam method take a parameter name and a parameter value as arguments, so no way to miss one. However, you
 * need to call this method once for each parameter that need to be added. In order to produce less code, the addParam method return the OPParams object, so
 * that it is possible to chain the calls, like this:
 * <pre>
 * OPParams myParams = new OPParams();
 * myParams.addParam(&quot;name1&quot;, &quot;value1&quot;).addParams(&quot;name2&quot;, &quot;value2&quot;).addParams(&quot;name3&quot;, &quot;value3&quot;);
 * </pre>
 *
 * Once an OPParams object has been built, it is necessary to transform it into an {@link OPParamsJS} object in order to be transfered between parts (it is
 * necessary to use a real javascript object (a GWT JavaScriptObject) because passing "java" object between differents GWT modules (as parts are) gives very
 * strange results, thanks to GWT compiler optimisations). There are helpers methods to transform an OPParams object into a {@link OPParamsJS} one, and
 * vice-versa.
 * 
 * @author Christophe Bouthier [chris{AT}binary-gastronome{DOT}fr]
 * @creation.date 19 December 2009
 */
public class OPParams {

    /**
     * The map containing the params names and values (names as key, values as values).
     */
    protected final Map<String, String> params = new HashMap<String, String>();

    /**
     * Constructor. Can either take null as argument, or an even number of Strings. It is the same to pass null or to pass an empty array of String to
     * namesAndValues. When using the varargs, each String at an odd place is a parameter name, and each other String is the corresponding parameter value. For
     * example:
     * <pre>
     * OPParams myParams = new OPParams(&quot;paramName1&quot;, &quot;paramValueForName1&quot;, &quot;paramName2&quot;, &quot;paramValueForName2&quot;);
     * </pre>
     * 
     * Even if namesAndValues is not null, it is possible to add other parameters using {@link #addParam(String, String)}.
     * 
     * @param namesAndValues
     *            the names and values of added parameters. Can be null or empty, but if not null, should contains even number of Strings. Names and values shouldn't be null. Names shouldn't be empty, values can.
     */
    public OPParams(final String... namesAndValues) {
        if (namesAndValues != null) {
            assert (namesAndValues.length % 2) == 0 : "There should be an even number of String passed as argument, or null";

            fillUpParamsWithArray(namesAndValues);
        }
    }

    /**
     * Fills up the params map with the given array, using each odd String as keys and each even String as corresponding value.
     * 
     * @param namesAndValues
     *            the name and values of added parameters. Shouldn't be null. Can be empty. Should contains even number of Strings. Names and values shouldn't be null. Names shouldn't be empty, values can.
     */
    private void fillUpParamsWithArray(final String[] namesAndValues) {
        boolean isName = true;
        String name = null;
        for (String param : namesAndValues) {
            if (isName) {
                name = param;
                isName = false;
            } else {
                assert name != null : "a parameter name cannot be null";
                assert name != "" : "a parameter name cannot be empty";
                assert param != null : "a parameter value cannot be null";
                params.put(name, param);
                isName = true;
            }
        }
    }

    /**
     * Add a tuple (name, value) as a new parameter. If there's alreay a parameter with the same name, a new parameter won't be added. Instead, the old value of
     * the parameter will be replaced with the given value.
     * <p>
     * The method return the instance of the OPParams object modified, so that calls can be chained, like this:
     * <pre>
     * OPParams myParams = new OPParams();
     * myParams.addParam(&quot;name1&quot;, &quot;value1&quot;).addParams(&quot;name2&quot;, &quot;value2&quot;).addParams(&quot;name3&quot;, &quot;value3&quot;);
     * </pre>
     * 
     * @param paramName
     *            the name of the added param. Shouldn't be null, shouldn't be empty.
     * @param paramValue
     *            the value of the added param. Shouldn't be null. Can be empty.
     * @return the instance of the modified OPParams, so that calls can be chained. Cannot be null.
     */
    public OPParams addParam(final String paramName, final String paramValue) {
        assert paramName != null : "parameter paramName shouldn't be null";
        assert paramName != "" : "parameter paramName shouldn't be empty";
        assert paramValue != null : "parameter paramValue shouldn't be null";

        params.put(paramName, paramValue);

        return this;
    }

    /**
     * Returns the array of all params names. There is no guarantee on the order. The set is empty if OPParams is empty, but is never null.
     * 
     * @return the array of all params names. Can be empty, but cannot be null.
     */
    public String[] getNames() {
        final Set<String> rNames = params.keySet();
        final String[] names = rNames.toArray(new String[rNames.size()]);

        return names;
    }

    /**
     * Returns the param value for the given param name. Return null if there is no param with such a name.
     * 
     * @param paramName
     *            name of the searched parameter. Shouldn't be null.
     * @return the value of the searched param, or null if there is no param with such a name.
     */
    public String getParamValue(final String paramName) {
        assert paramName != null : "parameter paramName shouldn't be null"; 
        assert paramName != "" : "parameter paramName shouldn't be empty"; 

        return params.get(paramName);
    }

    /**
     * Returns the number of params, that is the number of tuples (name, value).
     * 
     * @return the number of params. Can be 0, cannot be negative.
     */
    public int size() {
        return params.size();
    }
    
    /**
     * Returns a human-readable description of this params content. Useful for debugging. Called description to be unified with OPPParamsJS (and cannot override toString in OPParamsJS as it is final in
     * JavaScriptObject).
     * 
     * @return a human-readable description of the params, useful for debugging.
     */
    public final String getDescription() {
        String description = "[";
        final String[] names = getNames();
        for (int i = 0; i < names.length; i++) {
            String name = names[i];
            String value = getParamValue(name);
            if (i != 0) {
                description += ", ";
            }
            description += "(" + name + ":" + value + ")";
        }
        return description + "]";
    }

}
